﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace XNAssist
{
    /// <summary>
    /// A dictionary that holds a "buffer" that allows it to add or remove items AFTER you iterate through it in a game loop
    /// </summary>
    /// <typeparam name="TKey"> The type of the key </typeparam>
    /// <typeparam name="TValue"> The type of the value </typeparam>
    public class BufferedDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
    {
        private readonly Dictionary<TKey, TValue> _items;

        private readonly Dictionary<TKey, TValue> _toAdd;

        private readonly List<TKey> _toRemove;

        /// <summary>
        /// The number of items in this BufferedDictionary
        /// </summary>
        public int Count
        {
            get
            {
                return _items.Count(item => !_toRemove.Contains(item.Key)) + _toAdd.Count(item => !_toRemove.Contains(item.Key));
            }
        }

        /// <summary>
        /// A simple constructor
        /// </summary>
        public BufferedDictionary()
        {
            _items = new Dictionary<TKey, TValue>();
            _toAdd = new Dictionary<TKey, TValue>();
            _toRemove = new List<TKey>();
        }

        // TODO: Determine if we should use the added items as well
        public TValue this[TKey key]
        {
            get { return _items[key]; }
            set { _items[key] = value; }
        }

        /// <summary>
        /// Updates the dictionary by applying the buffers: call this in the game loop. After you do everything else is a good idea.
        /// </summary>
        public void Update()
        {
            foreach (var item in _toRemove)
                _items.Remove(item);

            foreach (var item in _toAdd)
            {
                _items.Add(item.Key, item.Value);
            }

            _toAdd.Clear();
            _toRemove.Clear();
        }

        /// <summary>
        /// Adds an item to the dictionary's buffer, so it can be added the next time Update is called.
        /// </summary>
        /// <param name="key"> The key to add </param>
        /// <param name="value"> The value to add </param>
        public void Add(TKey key, TValue value)
        {
            // If the key is in either collection and is NOT in the list to be removed, don't let it be added
            if ((_items.ContainsKey(key) || _toAdd.ContainsKey(key)) && !_toAdd.ContainsKey(key))
                throw new ArgumentException("There is already an item with this key in this BufferedDictionary");

            _toAdd.Add(key, value);
        }

        /// <summary>
        /// Adds an item to the dictionary directly, bypassing the buffer (WARNING: NOT ITERATOR-SAFE!)
        /// </summary>
        /// <param name="key"> The key to add </param>
        /// <param name="value"> The value to add </param>
        public void AddDirect(TKey key, TValue value)
        {
            // If the key is in either collection and is NOT in the list to be removed, don't let it be added
            if ((_items.ContainsKey(key) || _toAdd.ContainsKey(key)) && !_toAdd.ContainsKey(key))
                throw new ArgumentException("There is already an item with this key in this BufferedDictionary");

            _items.Add(key, value);
        }

        /// <summary>
        /// Moves all items in this list into the removal buffer
        /// </summary>
        public void Clear()
        {
            _toAdd.Clear();

            foreach (var item in _items)
                _toRemove.Add(item.Key);
        }

        /// <summary>
        /// Clears all items in the list instantly, bypassing the buffer (WARNING: NOT ITERATOR-SAFE!)
        /// </summary>
        public void ClearDirect()
        {
            _toAdd.Clear();
            _items.Clear();
            _toRemove.Clear();
        }

        /// <summary>
        /// Finds whether this dictionary contains the given key
        /// </summary>
        /// <param name="key"> The key to check for </param>
        /// <returns> Whether this dictionary contains the given key </returns>
        public bool ContainsKey(TKey key)
        {
            return (_items.ContainsKey(key) || _toAdd.ContainsKey(key)) && !_toRemove.Contains(key);
        }

        /// <summary>
        /// Finds whether this dictionary contains the given value
        /// </summary>
        /// <param name="value"> The value to check for </param>
        /// <returns> Whether this dictionary contains the given value </returns>
        public bool ContainsValue(TValue value)
        {
            return _items.Any(item => item.Value.Equals(value) && !_toRemove.Contains(item.Key)) || _toAdd.Any(item => item.Value.Equals(value) && !_toRemove.Contains(item.Key));
        }

        /// <summary>
        /// Removes the given key (and it's value) from the dictionary by placing it into the removal buffer
        /// </summary>
        /// <param name="key"> The key to remove </param>
        public void Remove(TKey key)
        {
            _toRemove.Add(key);
        }

        /// <summary>
        /// Removes the given key (and it's value) from the dictionary instantly, bypassing the buffer (WARNING: NOT ITERATOR-SAFE!)
        /// </summary>
        /// <param name="key"> The key to remove </param>
        public void RemoveDirect(TKey key)
        {
            _items.Remove(key);
        }

        /// <summary>
        /// Gets the enumerator for this dictionary (doesn't contain the addition/removal buffer)
        /// </summary>
        /// <returns> The enumerator for this dictionary </returns>
        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return _items.GetEnumerator();
        }

        /// <summary>
        /// Gets the enumerator for this dictionary (doesn't contain the addition/removal buffer)
        /// </summary>
        /// <returns> The enumerator for this dictionary </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}
